package org.zhuzx.util;

import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.encoders.Hex;

import java.nio.charset.StandardCharsets;

/**
 * 代码全部来自网络，未整理。
 * AES encryption and decryption tool.
 * @author ben 2014年3月20日
 */
public class AesTool {

	private static final byte[] initVector = { 0x32, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31,
			0x38, 0x27, 0x36, 0x35, 0x33, 0x23, 0x32, 0x31 };

	/**
	 * Encrypt the content with a given key using aes algorithm.
	 * 
	 * @param content
	 * @param key
	 * 			must contain exactly 32 characters
	 * @return
	 * @
	 */
	public static String encrypt(String content, String key) {
		if (key == null) {
			throw new IllegalArgumentException("Key cannot be null!");
		}
		String encrypted = null;
		byte[] keyBytes = key.getBytes();
		if (keyBytes.length != 32 && keyBytes.length != 24
				&& keyBytes.length != 16) {
			throw new IllegalArgumentException(
					"Key length must be 128/192/256 bits!");
		}
		byte[] encryptedBytes = null;
		encryptedBytes = encrypt(content.getBytes(), keyBytes, initVector);
		encrypted = new String(Hex.encode(encryptedBytes));
		return encrypted;
	}

	/**
	 * Decrypt the content with a given key using aes algorithm.
	 * 
	 * @param content
	 * @param key
	 * 			must contain exactly 32 characters
	 * @return
	 * @
	 */
	public static String decrypt(String content, String key) {
		if (key == null) {
			throw new IllegalArgumentException("Key cannot be null!");
		}
		String decrypted = null;
		byte[] encryptedContent = Hex.decode(content);
		byte[] keyBytes = key.getBytes();
		byte[] decryptedBytes = null;
		if (keyBytes.length != 32 && keyBytes.length != 24
				&& keyBytes.length != 16) {
			throw new IllegalArgumentException(
					"Key length must be 128/192/256 bits!");
		}
		decryptedBytes = decrypt(encryptedContent, keyBytes, initVector);
		decrypted = new String(decryptedBytes, StandardCharsets.UTF_8);
		return decrypted;
	}

	/**
	 * Encrypt data.
	 * 
	 * @param plain
	 * @param key
	 * @param iv
	 * @return
	 * @throws Exception
	 */
	public static byte[] encrypt(byte[] plain, byte[] key, byte[] iv) {
		PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(
				new CBCBlockCipher(new AESFastEngine()));
		CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),
				iv);
		aes.init(true, ivAndKey);
		return cipherData(aes, plain);
	}

	/**
	 * Decrypt data.
	 * 
	 * @param cipher
	 * @param key
	 * @param iv
	 * @return
	 * @throws Exception
	 */
	public static byte[] decrypt(byte[] cipher, byte[] key, byte[] iv)
			{
		PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(
				new CBCBlockCipher(new AESFastEngine()));
		CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key),
				iv);
		aes.init(false, ivAndKey);
		return cipherData(aes, cipher);
	}

	/**
	 * Encrypt or decrypt data.
	 * 
	 * @param cipher
	 * @param data
	 * @return
	 * @throws Exception
	 */
	private static byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data)
			{
		int minSize = cipher.getOutputSize(data.length);
		byte[] outBuf = new byte[minSize];
		int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
		try {
			int length2 = cipher.doFinal(outBuf, length1);
			int actualLength = length1 + length2;
			byte[] result = new byte[actualLength];
			System.arraycopy(outBuf, 0, result, 0, result.length);
			return result;
		} catch (InvalidCipherTextException e) {
			throw new RuntimeException(e);
		}
	}

	public static void main(String[] args) {
		String key = "00BFC3FD9AA8A1D0F025F4493AA47180";
		System.out.println("use key: " + key);
		String data = "a test string for AES. 用于测试AES的字符串。";
		String encrypted = AesTool.encrypt(data, key);
		System.out.println("encrypted: \n" + encrypted);
		System.out.println("encrypted length: " + encrypted.length());
		String decrypted = AesTool.decrypt(encrypted, key);
		System.out.println("decrypted: \n" + decrypted);
		System.out.println("decrypted length: " + decrypted.length());
		boolean isSuccessful = decrypted.equals(data);
		System.out.println(isSuccessful);
	}
}